package com.poc.poc.compoment;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.time.Duration;
import java.util.concurrent.TimeUnit;

@Component
public class DistributedRedisLock implements ApplicationContextAware {

    //private volatile static DistributedRedisLock redisLock;

    private ApplicationContext context;

    private StringRedisTemplate stringRedisTemplate;

 /*   private DistributedRedisLock(){}

    public static DistributedRedisLock getInstance(){
        if(redisLock==null){
            synchronized(DistributedRedisLock.class) {
                if(redisLock==null){
                    redisLock = new DistributedRedisLock();
                }
            }
        }
        return redisLock;
    }*/



    private long timeout = 10*1000;

    public Boolean tryLock(String lockName){
        stringRedisTemplate = (StringRedisTemplate)context.getBean("stringRedisTemplate");
        // 直接设置锁
        String lockValue = Thread.currentThread().getId() + "_" + (System.currentTimeMillis()+timeout);
        Boolean isLock = stringRedisTemplate.opsForValue().setIfAbsent(lockName, lockValue, timeout, TimeUnit.MILLISECONDS);
        //如果设置锁失败，则判断锁是否已经超时
        if(!isLock){
            lockValue = stringRedisTemplate.opsForValue().get(lockName);
           if(lockValue!=null){
               Long time= Long.valueOf(lockValue.split("_")[1]);
               if(time<System.currentTimeMillis()){
                   //如果锁已经超时了，但是没有解锁
                   this.unLock(lockName, false);
                   //try to lock again
                   lockValue = Thread.currentThread().getId() + "_" + (System.currentTimeMillis()+timeout);
                   isLock = stringRedisTemplate.opsForValue().setIfAbsent(lockName, lockValue, timeout, TimeUnit.MILLISECONDS);
                   //此时，仍然可能获取锁失败，在delete的时候，被别的线程抢占了锁，但是仍然只有一个线程获取锁，是安全的。
               }
           }
        }
        if(isLock){
            this.watchDog(lockName);
        }
        return isLock;

    }


    public Boolean unLock(String lockName, Boolean toCheckOwnerthread){
        if(stringRedisTemplate.hasKey(lockName)){
            if(toCheckOwnerthread){
                String lockValue = stringRedisTemplate.opsForValue().get(lockName);
                String threadId = lockValue.split("_")[0];
                if(Long.valueOf(threadId).equals(Thread.currentThread().getId())){
                    return stringRedisTemplate.delete(lockName);
                }else{
                    return false;
                }
            }else{
                return stringRedisTemplate.delete(lockName);
            }
        }else {
            return true;
        }
    }

    public void watchDog(String lockName){
        Thread watchDogthread = new Thread(()->{
            try {
                while (true){
                    String lockValue = stringRedisTemplate.opsForValue().get(lockName);
                    if(lockValue==null) return;
                    Long time = Long.valueOf(lockValue.split("_")[1]);
                    if(time+4*1000>System.currentTimeMillis()){
                        stringRedisTemplate.expire(lockName, Duration.ofMillis(timeout));
                        System.out.println("锁续期成功");

                    }
                    Thread.sleep(2*1000);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
                throw new RuntimeException(e);
            }
        });
        watchDogthread.setDaemon(true);
        watchDogthread.start();
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.context = applicationContext;
        stringRedisTemplate = (StringRedisTemplate)context.getBean("stringRedisTemplate");
    }
}
